﻿//===============================================================================
// Microsoft patterns & practices Enterprise Library Contribution
// Query Application Block
//===============================================================================

using System;
using System.Data;
using System.Reflection;
using System.Globalization;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using EntLibContrib.Query.Configuration;
using EntLibContrib.Query.Properties;

namespace EntLibContrib.Query.Commands
{
	/// <summary>
	/// Simple abstract class to hold the components of a service command
	/// </summary>
	[ConfigurationElementType(typeof(ServiceCustomCommandData))]
	public abstract class ServiceCommand : Command
	{
		#region Fields
		private string endpoint;
		private string address;
		private string service;
		private string method;
		private object serviceObject;
		private MethodInfo methodInfo;
		#endregion

		#region Properties
		/// <summary>
		/// Gets the service endpoint.
		/// </summary>
		/// <value>The service endpoint.</value>
		public string Endpoint
		{
			get { return endpoint; }
			set { endpoint = value; }
		}

		/// <summary>
		/// Gets the service address.
		/// </summary>
		/// <value>The service address.</value>
		public string Address
		{
			get { return address; }
			set { address = value; }
		}

		/// <summary>
		/// Gets or sets the service.
		/// </summary>
		/// <value>The service.</value>
		public string Service
		{
			get { return service; }
			set { service = value; }
		}

		/// <summary>
		/// Gets or sets the method.
		/// </summary>
		/// <value>The method.</value>
		public string Method
		{
			get { return method; }
			set { method = value; }
		}

		/// <summary>
		/// Gets an instance of the service object.
		/// </summary>
		/// <value>The service object.</value>
		/// <remarks>override this property if your service constructor takes parameters</remarks>
		public virtual object ServiceObject
		{
			get
			{
				if (serviceObject == null)
					if (!String.IsNullOrEmpty(service))
						serviceObject = Activator.CreateInstance(Type.GetType(service, true, true));

				return serviceObject;
			}
			set { serviceObject = value; }
		}

		/// <summary>
		/// Gets the reflected method information.
		/// </summary>
		/// <value>The method information.</value>
		public virtual MethodInfo MethodInfo
		{
			get
			{
				if (methodInfo == null)
					if (!String.IsNullOrEmpty(method))
						methodInfo = ServiceObject.GetType().GetMethod(method);

				return methodInfo;
			}
		}

		/// <summary>
		/// Determines whether the service object is set.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if the service object is set; otherwise, <c>false</c>.
		/// </value>
		/// <returns>
		/// 	<c>true</c> if the service object is set; otherwise, <c>false</c>.
		/// </returns>
		protected bool IsServiceObjectSet
		{
			get { return (serviceObject == null); }
		}
		#endregion

		#region Construction
		/// <summary>
		/// Initializes a new instance of the <see cref="ServiceCommand"/> class.
		/// </summary>
		protected ServiceCommand()
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="ServiceCommand"/> class.
		/// </summary>
		/// <param name="address">The address.</param>
		/// <param name="service">The service.</param>
		/// <param name="method">The method.</param>
		/// <param name="parameters">The parameters.</param>
		protected ServiceCommand(string address, string service, string method, ParameterDictionary parameters)
			: base(parameters)
		{
			ValidateAndStoreParameters("HttpConnector", address, service, method);
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="ServiceCommand"/> class.
		/// </summary>
		/// <param name="endpoint">The endpoint.</param>
		/// <param name="address">The address.</param>
		/// <param name="service">The service.</param>
		/// <param name="method">The method.</param>
		/// <param name="parameters">The parameters.</param>
		protected ServiceCommand(string endpoint, string address, string service, string method, ParameterDictionary parameters)
			: base(parameters)
		{
			ValidateAndStoreParameters(endpoint, address, service, method);
		}
		#endregion

		#region Public Methods
		/// <summary>
		/// Executes a read action.
		/// </summary>
		/// <returns>
		/// a DataSet containing the results of the read action
		/// </returns>
		public override DataSet ExecuteForRead()
		{
			// invoke the service method which must return a DataSet
			return (DataSet)MethodInfo.Invoke(ServiceObject, ConvertParameters());
		}

		/// <summary>
		/// Executes a write action.
		/// </summary>
		public override void ExecuteForWrite()
		{
			// invoke the service method which must return void
			MethodInfo.Invoke(ServiceObject, ConvertParameters());
		}
		#endregion

		#region Private Methods
		/// <summary>
		/// Validates the and store parameters.
		/// </summary>
		/// <param name="endpointName">Name of the endpoint.</param>
		/// <param name="addressName">Name of the address.</param>
		/// <param name="serviceName">Name of the service.</param>
		/// <param name="methodName">Name of the method.</param>
		private void ValidateAndStoreParameters(string endpointName, string addressName, string serviceName, string methodName)
		{
			// check for a null or empty endpoint
			if (string.IsNullOrEmpty(endpointName))
				throw new ArgumentNullException("endpointName", InternalResources.ExceptionServiceCommandEndpointNullOrEmpty);

			// check for a null or empty service name and method
			if (string.IsNullOrEmpty(serviceName))
				throw new ArgumentNullException("serviceName", InternalResources.ExceptionServiceCommandServiceNullOrEmpty);

			// check that the service name represents a real type
			Type serviceType;
			try
			{
				serviceType = Type.GetType(serviceName, true, true);
			}
			catch (Exception exception)
			{
				throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, InternalResources.ExceptionServiceCommandServiceInvalid, serviceName), exception);
			}

			// check for a null or empty method name
			if (string.IsNullOrEmpty(methodName))
				throw new ArgumentNullException("methodName", InternalResources.ExceptionServiceCommandMethodNullOrEmpty);

			// check that the method is contained within the service type and is public
			if (serviceType != null)
			{
				if (serviceType.GetMethod(methodName) == null)
					throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, InternalResources.ExceptionServiceCommandMethodInvalid, methodName), methodName);
			}

			// store the service address
			this.endpoint = endpointName;
			// store the service address
			this.address = addressName;
			// store the service type name
			this.service = serviceName;
			// store the service method
			this.method = methodName;
		}

		/// <summary>
		/// Converts the parameters from a <see cref="ParameterDictionary"/> to an <see cref="System.Object"/> array.
		/// </summary>
		/// <returns>
		/// A <see cref="System.Object"/> array built from the parameter dictionary
		/// </returns>
		private object[] ConvertParameters()
		{
			// get the parameter array from the method in the correct order
			ParameterInfo[] methodParameters = MethodInfo.GetParameters();
			if (methodParameters.Length == 0)
				return null;
			// create the outbound parameter array
			object[] parameterArray = new object[methodParameters.Length];
			// match the parameters with their values
			int index = 0;
			foreach (ParameterInfo methodParameter in methodParameters)
			{
				parameterArray[index++] = Parameters[methodParameter.Name].Value;
			}
			// return the parameter array
			return parameterArray;
		}
		#endregion
	}
}
